#ifndef _DISASSEMBLER_H_
#define _DISASSEMBLER_H_

#include <cstdlib>
#include <string>
#include <string_view>
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif

#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInstPrinter.h"
#if LLVM_VERSION_MAJOR >= 14
#include "llvm/MC/TargetRegistry.h"
#if LLVM_VERSION_MAJOR >= 15
#include "llvm/MC/MCSubtargetInfo.h"
#endif
#else
#include "llvm/Support/TargetRegistry.h"
#endif
#include "llvm/Support/TargetSelect.h"

#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif

#if LLVM_VERSION_MAJOR < 11
#error Please use LLVM with major version >= 11
#endif

static llvm::MCDisassembler *gDisassembler = nullptr;
static llvm::MCSubtargetInfo *gSTI = nullptr;
static llvm::MCInstPrinter *gIP = nullptr;

class Disassembler {
private:
    llvm::MCDisassembler* pDisassembler;
    llvm::MCSubtargetInfo* pSTI;
    llvm::MCInstPrinter* pIP;
    llvm::MCAsmInfo* pAsmInfo;
    llvm::MCContext* pCtx;
public:
    Disassembler(const char *triple) {
        llvm::InitializeAllTargetInfos();
        llvm::InitializeAllTargetMCs();
        llvm::InitializeAllAsmParsers();
        llvm::InitializeAllDisassemblers();

        std::string errstr;
        std::string gTriple(triple);

        auto target = llvm::TargetRegistry::lookupTarget(gTriple, errstr);
        if (!target) {
            llvm::errs() << "Can't find target for " << gTriple << ": "
                         << errstr << "\n";
            assert(0);
        }

        llvm::MCTargetOptions MCOptions;
        pSTI = target->createMCSubtargetInfo(gTriple, "", "");
        std::string isa = target->getName();
        if (isa == "riscv32" || isa == "riscv64") {
            pSTI->ApplyFeatureFlag("+m");
            pSTI->ApplyFeatureFlag("+a");
            pSTI->ApplyFeatureFlag("+c");
            pSTI->ApplyFeatureFlag("+f");
            pSTI->ApplyFeatureFlag("+d");
        }
        llvm::MCInstrInfo *gMII = gMII = target->createMCInstrInfo();
        llvm::MCRegisterInfo *gMRI = target->createMCRegInfo(gTriple);
        pAsmInfo = target->createMCAsmInfo(*gMRI, gTriple, MCOptions);
#if LLVM_VERSION_MAJOR >= 13
        auto llvmTripleTwine = llvm::Twine(triple);
        auto llvmtriple = llvm::Triple(llvmTripleTwine);
        pCtx = new llvm::MCContext(llvmtriple, pAsmInfo, gMRI, nullptr);
#else
        auto Ctx = new llvm::MCContext(AsmInfo, gMRI, nullptr);
#endif
        pDisassembler = target->createMCDisassembler(*pSTI, *pCtx);
        pIP = target->createMCInstPrinter(llvm::Triple(gTriple),
                                          pAsmInfo->getAssemblerDialect(),
                                          *pAsmInfo, *gMII, *gMRI);
        pIP->setPrintImmHex(true);
        pIP->setPrintBranchImmAsAddress(true);
        if (isa == "riscv32" || isa == "riscv64")
            pIP->applyTargetSpecificCLOption("no-aliases");
    }
    ~Disassembler() {
        delete pDisassembler;
        delete pSTI;
        delete pIP;
        delete pAsmInfo;
        delete pCtx;
    }
    std::string disassemble(uint64_t pc, uint8_t *code, int nbyte = 4) {
        llvm::MCInst inst;
        llvm::ArrayRef<uint8_t> arr(code, nbyte);
        uint64_t dummy_size = 0;
        pDisassembler->getInstruction(inst, dummy_size, arr, pc, llvm::nulls());

        std::string s;
        llvm::raw_string_ostream os(s);
        pIP->printInst(&inst, pc, "", *pSTI, os);

        int skip = s.find_first_not_of('\t');
        return s.substr(skip);
    }
};

#endif
